﻿#include "editor/drawing/area.h"
#include "editor/drawing/toolbox.h"
#include "editor/settings.h"

#include "vg/base.h"
#include "vg/stroke.h"

#include "target.h"
#include "texture.h"
#include "draw.h"

using namespace std;
using namespace anip;

const float scrollZoomFactor = 1.1f;
const float scrollFactor = 25.0f;

editor::DrawingArea::DrawingArea(Container* parent, SFrameGroup& fg) :
	EditorWindow(parent, { 500, 500 }, L"绘图区")
{
	_currentVfg = dynamic_cast<vg::VgFrameGroup*>(fg.pointer());
	if (_currentVfg == nullptr)
		throw invalid_argument("Not a VgFrameGroup");
	setText(L"绘图区 ["s + fg.name + L"]");

	if (Globals::drawingToolbox == nullptr)
		new DrawingToolbox(parent);
	//if (Globals::drawingToolbar == nullptr)
	//	new DrawingToolBar(parent);
	Globals::currentDrawingArea = this;

	auto& btn = addCloseButton();
	btn.texture = Settings::closeButtonTexture.get();

	vec2 bufferSize = Context::findCurrentMonitor().resolution();
	_cxt = GraphicsRenderContext(bufferSize);
	_buffer = new RenderBuffer((int)bufferSize.x, (int)bufferSize.y);
	_vfgRenderer = _currentVfg->createRenderer();
	_vfgRenderer->activate();

	vg::Stroke* str = new vg::Stroke();
	str->points.push_back({ vec2{ 0 } });
	str->points.push_back({ vec2{ 0.5f } });
	str->calcNormal();
	_currentVfg->primitives.push_back(str);
	_vfgRenderer->update();

	_render();

	_panel = new ui::Draggable<ui::Panel>(this, { 0, 0 }, _size - _margin);
	_panel->userCanScroll = false;

	// ANIP_FIXME: hideous (?)
	_panel->onScroll = [&](auto&, auto... args) { _scroll(args...); };
	_panel->onMouseMove = [&](auto&, auto... args) { _mouseMove(args...); };
	_panel->onMouseButton = [&](auto&, auto... args) { _mouseButton(args...); };
	_panel->onBeginDrag = [&](auto&, auto... args) { return _canBeginDrag(args...); };
	_panel->onDrag = [&](auto&, auto... args) { _processDrag(args...); };
	_panel->onEndDrag = [&](auto&, auto... args) { _endDrag(args...); };
	_panel->onMouseLeave = [&](auto&) { postSignal({ ui::Signal::Type::NeedsRedraw }); };
}

void editor::DrawingArea::setSize(invec2 size)
{
	EditorWindow::setSize(size);

	_panel->setSize(_size - _margin);
	if (size.x > _buffer->width() || size.y > _buffer->height())
	{
		_buffer->finalize();
		delete _buffer;

		vec2 s = Context::findCurrentMonitor().resolution();
		if (s.x < size.x) s.x = size.x;
		if (s.y < size.y) s.y = size.y;

		_buffer = new RenderBuffer((int)s.x, (int)s.y);
		_cxt.setViewport(s);
	}
}

void editor::DrawingArea::updateRender()
{
	_render();
}

void editor::DrawingArea::_scroll(invec2 pos)
{
	if (Context::holdingControl())
	{
		vec2 center = toLocalPos(Context::mousePos());
		_cxt.transform = _cxt.transform.scaleAround(
			_cxt.screen2view(center), vec2{ pow(scrollZoomFactor, pos.y) });
	}
	else
		_cxt.transform.offset += pos * vec2{ 1, -1 } *scrollFactor / _cxt.aspectScale();
	_render();
}

void editor::DrawingArea::_mouseMove(invec2 pos)
{
	Context::setCursor(KnownCursor::None);
	bool shouldRender = false;
	vec2 vpos = _cxt.screen2view(pos);
	for (auto t : Globals::drawingTools)
		if (t->isActive() && t->mouseMove(vpos))
			shouldRender = true;
	if (shouldRender) _render();

	// ... gizmos are to be drawn anyway
	postSignal({ ui::Signal::Type::NeedsRedraw });
}

void editor::DrawingArea::_mouseButton(MouseButton btn, ButtonAction action)
{
	bool shouldRender = false;
	for (auto t : Globals::drawingTools)
		if (t->isActive() && t->mouseButton(btn, action))
			shouldRender = true;
	if (shouldRender) _render();
}

bool editor::DrawingArea::_canBeginDrag(MouseButton btn, invec2 pos)
{
	bool shouldDrag = false;
	vec2 vpos = _cxt.screen2view(pos);
	drawing::StylusData stylus{}; // not supported yet
	for (auto t : Globals::drawingTools)
		if (t->isActive() && t->beginStroke(*this, btn, vpos, stylus))
			shouldDrag = true;
	if (shouldDrag)
	{
		_render();
		return true;
	}

	if (btn == MouseButton::Middle)
	{
		_dragStartTransform = _cxt.transform;
		_specialDrag = true;
		_dragStart = pos;
		return true;
	}
	return false;
}

void editor::DrawingArea::_processDrag(invec2 start, invec2 pos)
{
	bool shouldRender = false;
	vec2 vpos = _cxt.screen2view(pos);
	drawing::StylusData stylus{}; // not supported yet
	for (auto t : Globals::drawingTools)
		if (t->isActive() && t->strokePoint(*this, vpos, stylus))
			shouldRender = true;

	if (_specialDrag)
	{
		shouldRender = true;
		_cxt.transform = _dragStartTransform;
		if (Context::holdingControl())
		{
			// zoom
			float d1 = math::distance(_size * 0.5f, _dragStart);
			float d2 = math::distance(_size * 0.5f, pos);
			float factor = pow(2, (d2 - d1) / math::length(_size) * 4);
			_cxt.transform = _cxt.transform.scaleAround(
				_cxt.screen2view(_size * 0.5f), vec2{ factor });
		}
		else if (Context::holdingAlt())
		{
			// ANIP_UNDONE rotate
		}
		else
		{
			// pan
			_cxt.transform.offset += 
				(pos - start) * vec2{ 1, -1 } / _cxt.aspectScale();
		}
	}

	if (shouldRender) _render();
}

void editor::DrawingArea::_endDrag()
{
	bool shouldRender = false;
	for (auto t : Globals::drawingTools)
		if (t->isActive() && t->endStroke(*this))
			shouldRender = true;
	if (shouldRender) _render();

	_specialDrag = false;
}

void editor::DrawingArea::_render()
{
	_vfgRenderer->update();
	_cxt.beginRender(*_buffer);

	// ANIP_UNDONE: put bg specification somewhere in vg
	Context::clear(Colors::white);

	for (auto t : Globals::drawingTools)
		if (t->isActive()) t->drawGlobalBackground(_cxt);

	// TODO: draw seq until this vg

	for (auto t : Globals::drawingTools)
		if (t->isActive()) t->drawFrameGroupBackground(_cxt);

	_vfgRenderer->render(_cxt);

	for (auto t : Globals::drawingTools)
		if (t->isActive()) t->drawForeground(_cxt);

	// TODO: draw other seq

	for (auto t : Globals::drawingTools)
		if (t->isActive()) t->drawOverlay(_cxt);

	_cxt.endRender();
	postSignal({ ui::Signal::Type::NeedsRedraw });
}

void editor::DrawingArea::paint()
{
	EditorWindow::paint();

	Context::setScissor(_panel->toAbsolutePos(), _panel->size());
	Draw::texture(_panel->toAbsolutePos(), _buffer->getTexture());
	Context::noScissor();

	if (_panel->hovered())
		for (auto t : Globals::drawingTools)
			if (t->isActive()) t->drawGizmo(Context::mousePos());
}
